#!/bin/bash
# 在线视频网站的下载工具
# author: yanjingtu
# date: 2020/3/17

# 用法：./get.sh [REFERER页面地址]
# 其中方括号中的内容为可选内容，有些网站会验证引用页是否为指定的URL，这时可以传递该参数

# ================ 全局变量定义区 ================
THISNAME=$0
SCRIPTNAME=$(basename $THISNAME)

# ================ 函数逻辑定义区 ================

############################################################
# echo输出颜色文本
#echo命令改变样式，以输出不同颜色的文本，必须有 -e 选项(开启echo中的转义)。
#文本终端的显示颜色可以使用“ANSI非常规字符序列”来生成。
#例如：echo -e "\033[44;37;5m ME\033[0m COOL"
#解释："\033[44;37;5m ME "设置背景为蓝色，前景为白色，闪烁光标，输出字符“ME”；
#      "\033[0m COOL"重新设置屏幕到缺省设置，输出字符 “COOL”。
#      "e"是命令echo的一个可选项，它用于激活特殊字符的解析器。"\033"引导非常规字符序列(即"\033["表示终端转义字符开始，"\033"即退出键<ESC>的ASCII码)。"m"意味着设置属性然后结束非常规字符序列，这个例子里真正有效的字符是"44;37;5"和"0"。修改"44;37;5"可以生成不同颜色的组合，数值和编码的前后顺序没有关系。
#   
#    可以选择的编码如下所示(这些颜色是ANSI标准颜色)：
#          编码          颜色/动作
#          0      　     重新设置属性到缺省设置
#          1     　      设置粗体
#          2     　      设置一半亮度(模拟彩色显示器的颜色)
#          4     　      设置下划线(模拟彩色显示器的颜色)
#          5     　      设置闪烁
#          7     　      设置反向图象
#          22    　      设置一般密度
#          24    　      关闭下划线
#          25     　     关闭闪烁
#          27     　     关闭反向图象
#          30      　    设置黑色前景
#          31   　       设置红色前景
#          32   　       设置绿色前景
#          33   　       设置黄色前景
#          34   　       设置蓝色前景
#          35    　      设置紫色前景
#          36     　     设置青色前景
#          37    　      设置白色(灰色)前景
#          38      　    在缺省的前景颜色上设置下划线
#          39      　    在缺省的前景颜色上关闭下划线
#          40      　    设置黑色背景
#          41      　    设置红色背景
#          42     　     设置绿色背景
#          43     　     设置黄色背景
#          44     　     设置蓝色背景
#          45     　     设置紫色背景
#          46     　     设置青色背景
#          47      　    设置白色(灰色)背景
#          49      　    设置缺省黑色背景
#    其他有趣的代码还有：
#          \033[2J  　   清除屏幕
#          \033[0q  　   关闭所有的键盘指示灯
#          \033[1q 　    设置"滚动锁定"指示灯(Scroll Lock)
#          \033[2q 　    设置"数值锁定"指示灯(Num Lock)
#          \033[3q 　    设置"大写锁定"指示灯(Caps Lock)
#          \033[15:40H   把关闭移动到第15行，40列
#          \007  　　    发蜂鸣生beep
############################################################

# 向屏幕打印灰色文字
function EchoGray()
{
	echo -e "\e[1;36m$@\e[0m"
}

# 向屏幕打印红色文字
function EchoRed()
{
	echo -e "\e[1;31m$@\e[0m"
}

# 向屏幕打印蓝色文字
function EchoBlue()
{
	echo -e "\e[1;34m$@\e[0m"
}

# 向屏幕打印绿色文字
function EchoGreen()
{
	echo -e "\e[1;32m$@\e[0m"
}

# 向屏幕打印黄色背景的文字
function EchoYellow()
{
	echo -e "\e[1;33m\e[1;42m$@\e[0m"
}

# 向日志文件中输出内容
function WriteLog()
{
	logtype=$1
	shift

	if [ "$1" == "" ]; then
		return
	fi

	curdate=$(date "+%Y%m%d")
	tmhead=$(date "+%Y-%m-%d %H:%M:%S")
	shortname=${SCRIPTNAME%.*}
	logdir=/var/log/${shortname}
	logfile=${logdir}/${shortname}_${curdate}.log
	if ! [ -d ${logdir} ]; then
		mkdir -p ${logdir}
	fi
	# 向日志的最后追加一行
	echo -e "${tmhead}\t[${logtype}]$@" >> ${logfile}
	# 最后用彩色在屏幕上打印一下
	if [ "${logtype}" == "D" ]; then
		EchoGray $@
	elif [ "${logtype}" == "I" ]; then
		echo $@
	elif [ "${logtype}" == "N" ]; then
		EchoGreen $@
	elif [ "${logtype}" == "W" ]; then
		EchoYellow $@
	elif [ "${logtype}" == "E" ]; then
		EchoRed $@
	fi
}

# 记录日志并向屏幕打印灰色的文字（Debug类型）
function LogDebug()
{
	WriteLog D $@
}

# 记录日志并向屏幕打印无颜色的文字（Info类型）
function LogInfo()
{
	WriteLog I $@
}

# 记录日志并向屏幕打印绿色的文字（Notice类型）
function LogNotice()
{
	WriteLog N $@
}

# 记录日志并向屏幕打印黄色背景的文字（Warning类型）
function LogWarning()
{
	WriteLog W $@
}

# 记录日志并向屏幕打印红色的文字（Error类型）
function LogError()
{
	WriteLog E $@
}

# 下载一个文件，可能是m3u8文件，也可能是ts文件。函数会自动根据内容决定保存的路径。
# 如果是m3u8，还会自动解析内容并进一步下载（输出文件名增加前缀）。
function download_one()
{
	# 四个参数（后两个可选）：要下载的URL、保存到的本地路径和文件名、下一层的文件名前缀、引用页
	local url=$1
	local localpath=$2
	local prefix=$3
	local referer=$4

	# Debug信息
	LogDebug "Url: ${url} LocalPath: ${localpath} Referer: ${referer}  Prefix: ${prefix}"

	# URL和本地路径都不能为空
	if [ "${url}" == "" ]; then
		LogError "url cannot be null"
		return 0
	fi
	if [ "${localpath}" == "" ]; then
		LogError "localpath cannot be null"
		return 0
	fi

	# 如果本地已经有需要的文件了，则跳过下载该文件
	if [ -f ${localpath} ]; then
		LogWarning "skip ${localpath}"
		#return 0
	else
		# 如果本地目录不存在，则先创建一下目录
		pathname=$(dirname ${localpath})
		if ! [ -d ${pathname} ]; then
			LogNotice "create dir ${pathname}"
			mkdir -p ${pathname}
		fi

		# 下载文件到本地
		if [ "${referer}" == "" ]; then
			wget -nv -O ${localpath} ${url}
		else
			wget -nv -O ${localpath} --referer=${referer} ${url}
		fi
		# 如果下载该文件失败了，打印报错，删掉可能存在的错误文件，跳过当前文件的下载
		if [ $? -ne 0 ]; then
			LogError "Download-Failed: ${url}"
			rm -f ${localpath}
			return 1
		fi
	fi

	name=$(basename ${localpath})
	suffix=${name#*.}
	if [ "${suffix}" == "m3u8" ]; then
		isEncrypted=0
		# 如果文件中有加密秘钥，则下载该秘钥，并修改m3u8中的秘钥路径
		if [ "$(cat ${localpath} | grep '#EXT-X-KEY:')" != "" ]; then
			isEncrypted=1
			# get key
			# one line like: #EXT-X-KEY:METHOD=AES-128,URI="https://v2.91ceshi.com/res/72946866131566592/encrypt.key",IV=0x8d5c053547cc40349c4917cb433bfa63
			keyUrl=$(cat ${localpath} | grep '#EXT-X-KEY:' | sed 's/^.\+,URI="\(.\+\)".*$/\1/')
			# download key
			keyPath=key
			if ! [ -d ${keyPath} ]; then
				mkdir -p ${keyPath}
			fi
			keyFile=${keyPath}/${prefix}key.key
			download_one ${keyUrl} ${keyFile} ${prefix} ${referer}
			if [ $? -ne 0 ]; then
				LogError "下载m3u8的key文件失败，终止下载当前m3u8的后续内容: ${keyUrl}"
				return 1
			fi
			# change key in local m3u8-file
			sed -i "s\\${keyUrl}\\${keyFile}\\" ${localpath}
		fi

		# 依次下载m3u8中的每个文件
		i=1
		isVideo=0
		for onesub in $(cat ${localpath} | grep -v '#'); do
			# 针对不同类型的URL（完整URL、相对路径、绝对路径），分别做不同的处理（补全策略）
			if [ "${onesub:0:4}" == "http" ]; then
				suburl=${onesub}
			elif [ "${onesub:0:1}" == "/" ]; then
				protocol=$(echo ${url} | cut -d / -f 1)
				host=$(echo ${url} | cut -d / -f 3)
				suburl=${protocol}//${host}${onesub}
			else
				suburl=$(dirname ${url})/${onesub}
			fi
			subprefix="${prefix}${i}-"
			subname=$(basename ${onesub})
			subsuffix=${subname#*.}
			if [ "${subsuffix}" == "ts" ]; then
				path=video
				isVideo=1
			elif [ "${subsuffix}" == "m3u8" ]; then
				path=
			else
				path=other
			fi
			if [ "${path}" != "" ]; then
				if ! [ -d ${path} ]; then
					mkdir -p ${path}
				fi
				path=${path}/
			fi
			k=$(printf %07d ${i})
			localfile=${path}${prefix}${k}.${subsuffix}
			download_one ${suburl} ${localfile} ${subprefix} ${referer}
			if [ $? -ne 0 ]; then
				LogError "下载m3u8的分片文件失败，终止下载当前m3u8的后续内容: ${suburl}"
				return 1
			fi
			# 如果是从m3u8文件下载，则需要记得将m3u8中的文件URL修正成本地路径
			sed -i "s\\${onesub}\\${localfile}\\" ${localpath}
			let i=$i+1
		done
		# 合并一集结果文件
		bigfile=${path}${prefix}all.mp4
		if [ ${isEncrypted} -eq 1 ]; then
			#echo "ffmpeg -allowed_extensions ALL -i ${localpath} -c copy -bsf:a aac_adtstoasc ${bigfile}"
			ffmpeg -allowed_extensions ALL -i ${localpath} -c copy -bsf:a aac_adtstoasc ${bigfile}
		else
			#echo "ffmpeg -i ${localpath} -c copy ${bigfile}"
			ffmpeg -allowed_extensions ALL -i ${localpath} -c copy ${bigfile}
		fi
		# 如果合并成功的话就可以删掉零碎文件了，否则保留着不要删除
		if [ $? -eq 0 ]; then
			# TODO 如果视频分块用的扩展名不是ts，下面这句会导致无法删除分块文件，需要考虑改进
			rm -f ${path}${prefix}*.ts
		else
			LogError "合并视频片段失败！m3u8清单为${localpath}"
		fi
	fi
}

function download_by_list()
{
	referer=$1
	prefix=""
	m3u8localpath=.
	num=1
	for i in `cat list.txt | grep -v '#'`; do
		# 编号
		#j=${i%.*}
		j=`printf %02d ${num}`
		prefix=${j}-
		# 初始m3u8的完整URL
		url=$i
		# 下载到本地的m3u8的初始文件名
		onename=$(basename ${i})
		onesuffix=${onename#*.}
		onelocal=${m3u8localpath}/${j}.${onesuffix}
		# 下载
		download_one ${url} ${onelocal} ${prefix} ${referer}
		let num=$num+1
	done
}

function main()
{
	download_by_list $@
}

main $@
